package com.imooc.ecommerce.service.async;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.imooc.ecommerce.constant.GoodsConstant;
import com.imooc.ecommerce.entity.EcommerceGoods;
import com.imooc.ecommerce.goods.GoodsInfo;
import com.imooc.ecommerce.goods.SimpleGoodsInfo;
import com.imooc.ecommerce.mapper.EcommerceGoodsMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.imooc.ecommerce.constant.GoodsConstant.ECOMMERCE_GOODS_DICT_KEY;

/**
 * 异步服务接口实现
 *
 * @author zhangshao
 * @since 2023/11/30 20:45
 */
@Slf4j
@Service
@Transactional
public class AsyncServiceImpl implements IAsyncService {

    @Autowired
    private EcommerceGoodsMapper goodsMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 异步任务需要加上注解,并指定使用的线程池
     * 异步任务处理两件事情:
     * 1. 将商品信息保存到数据表
     * 2. 更新商品缓存
     *
     * @param goodsInfos
     * @param taskId
     */
    @Async("getAsyncExecutor")
    @Override
    public void asyncImportGoods(List<GoodsInfo> goodsInfos, String taskId) {
        log.info("async task running taskId :[{}]", taskId);
        StopWatch watch = StopWatch.createStarted();

        //1. 如果是goodsInfo中存在重复的商品,不保存; 直接返回,记录错误日志
        // 请求数据是否合法的标记
        boolean isIllegal = false;
        //将商品信息字段 joint在一起,用来判断是否存在重复
        Set<String> goodsJointInfos = new HashSet<>(goodsInfos.size());

        //过滤出来的, 可以入库的商品信息(规则按照自己的业务需求自定义即可)
        List<GoodsInfo> filterGoodsInfo = new ArrayList<>(goodsInfos.size());

        //走一遍循环,过滤非法参数与判定当前请求是否合法
        for (GoodsInfo goods : goodsInfos) {
            //基本条件不满足, 直接过滤掉
            if (goods.getPrice() <= 0 || goods.getSupply() <= 0) {
                log.info("goods info is invalid : [{}]", JSON.toJSONString(goods));
                continue;
            }
            //组合每一个商品信息
            String jointInfo = String.format(
                    "%s,%s,%s", goods.getGoodsCategory(), goods.getBrandCategory(), goods.getGoodsName()
            );
            if (goodsJointInfos.contains(jointInfo)) {
                isIllegal = true;
            }

            // 加入到两个容器中
            goodsJointInfos.add(jointInfo);
            filterGoodsInfo.add(goods);
        }

        //如果存在重复商品或者没有需要入库的商品,直接打印日志返回
        if (isIllegal || CollectionUtil.isEmpty(filterGoodsInfo)) {
            watch.stop();
            log.warn("ignore nothing : [{}]", JSON.toJSONString(filterGoodsInfo));
            log.info("check and import goods done: [{}]", watch.getTime(TimeUnit.MICROSECONDS));
            return;
        }

        List<EcommerceGoods> ecommerceGoods = filterGoodsInfo.stream().map(EcommerceGoods::to).collect(Collectors.toList());
        List<EcommerceGoods> targetGoods = new ArrayList<>(ecommerceGoods.size());

        // 2.保存 goodsInfo之前 先判断下是否存在重复商品

        ecommerceGoods.forEach(good -> {
            // limit1
            EcommerceGoods existGoods = new LambdaQueryChainWrapper<>(goodsMapper).eq(EcommerceGoods::getGoodsCategory, good.getGoodsCategory()).eq(EcommerceGoods::getBrandCategory, good.getBrandCategory()).eq(EcommerceGoods::getGoodsName, good.getGoodsName()).one();
            if (ObjectUtil.isNotEmpty(existGoods)) {
                return;
            }

            targetGoods.add(good);
        });

        //商品信息入库
        //todo 后期改为批量插入
        for (EcommerceGoods goods : targetGoods) {
            goodsMapper.insert(goods);
        }
        //将商品数据信息同步到Redis中
        saveNewGoodsInfoToRedis(targetGoods);
        log.info("save goods info to db and redis : [{}]", targetGoods.size());

        watch.stop();
        log.info("check and improt success : [{}ms]", watch.getTime(TimeUnit.MICROSECONDS));

    }

    /**
     * 将保存到数据库表中的数据缓存到redis中
     * dict: key --> <id,SimpleGoodsInfo(json)>
     *
     * @param saveGoods
     */
    private void saveNewGoodsInfoToRedis(List<EcommerceGoods> saveGoods) {
        // 由于 redis是内存存储,只存储简单商品信息
        List<SimpleGoodsInfo> simpleGoodsInfos = saveGoods.stream().map(EcommerceGoods::toSimple).collect(Collectors.toList());
        Map<String, String> id2JsonObject = new HashMap<>(simpleGoodsInfos.size());
        simpleGoodsInfos.forEach(
                good -> id2JsonObject.put(good.getId().toString(), JSON.toJSONString(good))
        );
        //保存到redis中
        redisTemplate.opsForHash().putAll(
                GoodsConstant.ECOMMERCE_GOODS_DICT_KEY,
                id2JsonObject
        );
    }
}
